/*****************************
 * customRig
 ******************************
 * This one is a lot--A LOT--different from the other controllers.  This is the only one (to date)
 * which supports iterative refinement.  This is a fundementally different approach than the other
 * controllers, which are "do it over again until it's right" controllers.  This controller will
 * allow you to record multiple passes...and any value that doesn't get modified during the current
 * recording pass will just assume that it's keeping it's old value (and this value will "play" as
 * the new values are recording).
 ******************************/

class customRig extends xbcPop   //need to make it accept EVERYONE and ANYONE
{
  //these will hold Point2D...only instead of X/Y it's Millisecond/Value
  //at a fixed 120x80 size, the screen can hold AT MOST 36...actually a little less
  //with Analog-selectors, multiplier slider, and "data" display
  //.....
  //To keep it a little more managable, I'm throttling it to 16...coincidentally
  //the max number of analogs a UDP controller can have in my Torque setup!
  ArrayList[] CUSTOMDATA = new ArrayList[16];

  int[] CUSTOMANALOG  = new int[16];

  boolean[] newCustomData = new boolean[16];

  customSlider[] custom;

  int numActive = 0;
  int lastClickedOn = -1;
  LaunchButton add;
  LaunchButton sub;
  LaunchButton export;

  int cSelX = w/2;
  int cSelY = 0;

  int controlDrag = -1;

  customRig(int f) //filenum
  {
    super(f);
    pg = createGraphics(width,height,JAVA2D);  //this one needs to be bigger...
    x = 0; 
    y = 0;
    w = width; 
    h = height;

    ANALOG = new int[16];
    DIGITAL = new boolean[] {
      false    };

    Arrays.fill(CUSTOMANALOG,127);

    Arrays.fill(newCustomData,true);

    dataX = w/2; 
    dataY = h-50;

    cSelX = w/2;
    cSelY = 150;

    //when these are created, ACTIVELAYER is one lower than it will be...
    add = new LaunchButton("Add Slider",new Message(true,ACTIVELAYER+1,0),0,0,cSelX-75,cSelY+170,70,30);
    sub = new LaunchButton("Drop Slider",new Message(false,ACTIVELAYER+1,0),0,0,cSelX+5,cSelY+170,70,30);    
    export = new LaunchButton("Save",new Message(true,ACTIVELAYER+1,3),0,0,width-80,5,50,25);

    custom = new customSlider[16];
    Arrays.fill(custom,null);
    for(int i = 0; i < 16; i++)
    {
      custom[i] = null;
      CUSTOMDATA[i] = new ArrayList();
    }
  }

  void mMessage(Message m)
  {
    if(m.target == 0)
    {
      println("Message received!");
      if(m.data.equals("true"))
      {
        addSlider();
      }
      else{
        layers.add(new PopConfirm(ACTIVELAYER++,2,"Drop slider?",0));
      }
    }else if(m.target == 1){
      if(!m.data.equals("")) custom[lastClickedOn].label = m.data;
    }else if(m.target == 2){
      if(m.data.equals("true")) dropSlider();
    }else if(m.target == 3){
      prepWrite();
    }else{
      println("Unrecognized Messsage!");
    }
  }

  void addSlider()
  {
    if(numActive < 16)
    {
      custom[numActive] = new customSlider(0,0,"UDP " + numActive);          
      numActive++;
    }
  }

  void addSlider(int sx, int sy, String l)
  {
    if(numActive < 16)
    {
      custom[numActive] = new customSlider(sx,sy,l);          
      numActive++;
    }    
  }

  void dropSlider()
  {
    numActive--;
    if(numActive < 0) numActive = 0;
  }

  void controlDraw()
  {
    pg.stroke(BUTTONSTROKE);
    pg.pushMatrix();
    pg.translate(cSelX,cSelY);
    for(int i = 0; i < 5; i++)
    {
      pg.fill(BUTTONFILL);    
      if(mouseX-cSelX > -75 + 30*i && mouseX-cSelX < -45 + 30*i && mouseY-cSelY > 205 && mouseY-cSelY < 235) pg.fill(BUTTONOVER);
      if(controlDrag == i) pg.fill(BUTTONDOWN);
      pg.rect(-75 + 30*i,205,30,30);
    }
    pg.popMatrix();   

    add.mDraw(pg);
    sub.mDraw(pg);
    export.mDraw(pg);

    for(int i = 0; i < numActive; i++)
    {
      if(custom[i].input != -1) custom[i].setSlider(slider[custom[i].input].getValue()*(invertSlider[custom[i].input]?-1:1));
      custom[i].mDraw(pg);
      CUSTOMANALOG[i] = getByteValue(custom[i].value);
    }

    if(controlDrag >= 0)
    {
      pg.stroke(255);
      pg.line(cSelX-60 + 30*controlDrag,cSelY+220,mouseX,mouseY);
    }
  }

  void closeDraw()
  {
    //draw the close button
    pg.textFont(font,18);
    pg.stroke(BUTTONSTROKE);
    pg.strokeWeight(1);
    if(mouseX > x+w-30 && mouseX < x+w-5 && mouseY > y+5 && mouseY < y+25)
    {
      if(mousePressed) pg.fill(BUTTONDOWN);
      else pg.fill(BUTTONOVER);
    }
    else{
      pg.fill(BUTTONFILL);
    }
    pg.rect(w-30,5,25,25);
    pg.fill(TEXTCOLOR);
    pg.text("X",w-22,25);  


    if(recording){
      pg.fill(255,0,0);
      pg.ellipse(w-30,h-30,30,30);

      for(int i = 0; i < numActive; i++)
      {
        if(custom[i].active)
        {
          if(newCustomData[i])  //is this the first time this channel has been modified?
          {
            newCustomData[i] = false;
            CUSTOMDATA[i] = new ArrayList();
            CUSTOMDATA[i].add(new Point(0,127));
          }
          Point p = (Point)CUSTOMDATA[i].get(CUSTOMDATA[i].size() - 1);
          if(p.y != getByteValue(custom[i].value)) CUSTOMDATA[i].add(new Point(millis()-startTime,getByteValue(custom[i].value)));
        }
        else{
          //if we're inactive, use the previously recorded value!
          float oldVal = getValueAtTime(i,millis()-startTime);
          CUSTOMANALOG[i] = (int)oldVal;    //why wait for another cycle?
          oldVal = (oldVal - 127.0)/128.0;
          custom[i].setSlider(oldVal);            
        }
      }
    }

    if(transmit)
    {
      //update every 1/20 of a second ATM...
      if(millis() - LASTUPDATE > 50){
        ANALOG = new int[numActive];
        for(int i = 0; i < numActive; i++)
        {
          ANALOG[i] = CUSTOMANALOG[i];
        }
        transmitUDP(0,DIGITAL, ANALOG);
        LASTUPDATE = millis(); 
      }
    }

    pg.endDraw();
    pg.modified = true;
  }

  int getValueAtTime(int whichValue, int whichTime)
  {
    if(whichTime < 0) return 127;  //homey don't play that

    if(CUSTOMDATA[whichValue].size() > 0)
    {
      Point lastPoint = null;
      for(int i = 0; i < CUSTOMDATA[whichValue].size(); i++)
      {
        Point p = (Point)CUSTOMDATA[whichValue].get(i);
        //if I hit it on the nose...return this value!
        if(p.x == whichTime) return p.y;

        //if i'm at the end of the line...return the last value!
        if(CUSTOMDATA[whichValue].size()-1 == i && p.x <= whichTime) return p.y;   

        //if I passed it, return the one before me!
        if(p.x > whichTime && lastPoint != null) return lastPoint.y;
        lastPoint = p;
      }
    }

    //if I asked for the value of an empty arraylist...
    return 127;
  }

  void mPress()
  {
    super.mPress();
    for(int i = numActive-1; i >= 0; i--)
    {
      if(custom[i].mPress())
      {
        lastClickedOn = i;
        return;
      }
    }

    controlDrag = -1;
    for(int i = 0; i < 5; i++)
    {
      if(mouseX-cSelX > -75 + 30*i && mouseX-cSelX < -45 + 30*i && mouseY-cSelY > 205 && mouseY-cSelY < 235) controlDrag = i;
    }
  }

  void mRelease()
  {
    super.mRelease();

    //counts down so the one on top (the last one drawn)
    //gets the first click
    for(int i = 0; i < numActive; i++)
    {
      custom[i].mRelease(controlDrag);
    }        

    add.mRelease();
    sub.mRelease();
    export.mRelease();
    controlDrag = -1;    
  }

  void mKeyUp()
  {

    if(key == ' '){
      recording = !recording;
      if(recording){
        startTime = millis();

        Arrays.fill(newCustomData,true);

        for(int i = 0; i < numActive; i++)
        {
          if(custom[i].active)
          {
            CUSTOMDATA[i] = new ArrayList();
            CUSTOMDATA[i].add(new Point(0,getByteValue(custom[i].value)));
            newCustomData[i] = false;
          }
        }      

      }
      else{
        //We don't want to write to a file just yet, because we may
        //be doing something with multiple passes!        
        println("Done recording!");
      }
    }

    if(key == 't'){
      transmit = !transmit;
    }

    if(keyCode == ESC){
      DONE = true;
      //tell the parent layer which fileNum we got up to
      messages.add(new Message(fileNum + "", ACTIVELAYER-1,21));
    }


  }

  void prepWrite()
  {
    //trick the superclass into thinking this is a well-behaving controller by 
    //sticking our piecemeal data into the standard data ArrayList
    ArrayList piecemeal = new ArrayList();
    ANALOG = new int[numActive];

    for(int i = 0; i < numActive; i++)
    {
      //if(CUSTOMDATA[i].size() == 0) println("Custom " + i + ": No points!");
      for(int j = 0; j < CUSTOMDATA[i].size(); j++)
      {
        Point p = (Point)CUSTOMDATA[i].get(j);
        piecemeal.add(constructDataPoint(p.x));
        //println("Custom " + i + ": " + p.x + " " + p.y);
      }
    }

    //okay...now we have all of our dataPoints...now we just need to put them in the right order!
    //this could be optimized...
    data = new ArrayList();
    while(piecemeal.size() > 0)
    {
      dataPoint p = null;
      for(int i = 0; i < piecemeal.size(); i++)
      {
        dataPoint d = (dataPoint)piecemeal.get(i);
        if(p == null || p.TIME > d.TIME) p = d;
      }
      piecemeal.remove(p);
      if(!data.contains(p))data.add(p);
    }

    //You can DO IT!
    if(data.size() > 0) writeState();
    else popWarn("No data!");
  }

  dataPoint constructDataPoint(int whichTime)
  {
    int[] d = new int[] {
      0    };  //digital not implemented (yet!)
    int[] a = new int[numActive];

    for(int i = 0; i < numActive; i++)
    {
      a[i] = getValueAtTime(i,whichTime);
    }

    return new dataPoint(0,whichTime,d,a);    
  }


}

//These are assumed to be housed in a Layer with width/height equal to the screen's width/height.
//If I should ever change this, the parentX and parentY should be added to the properties for proper
//mouse handling.
class customSlider
{
  float x,y,value;
  boolean active = false;
  String label;
  int input = -1;
  boolean invert = false;  

  //for physically dragging the whole thing
  float dragX;
  float dragY;
  boolean isMoving = false;

  //slider variables
  boolean dragActive = false;
  float sliderMin = 8;
  float sliderMax = 112;
  float sliderWidth = 8;
  float sliderX = 60;  //values range from 4 to 108

  int myLastClick = 0;

  customSlider(float _x, float _y, String l)
  {
    x = _x;
    y = _y;
    label = l;    
  }

  void mDraw(PGraphics pg)
  {
    pg.pushMatrix();
    pg.translate(x,y);
    if(isMoving) pg.translate(mouseX-dragX,mouseY-dragY);

    //border box
    pg.stroke(BORDERCOLOR);
    pg.strokeWeight(2);
    pg.fill(FILLCOLOR); 
    pg.rect(0,0,120,80);

    //on/off buttons
    pg.strokeWeight(1);
    if(!active) pg.fill(BUTTONDOWN);
    pg.rect(4,5,12,12);
    pg.fill(FILLCOLOR);
    if(active) pg.fill(BUTTONDOWN);
    pg.rect(18,5,12,12);
    pg.textAlign(CENTER);
    pg.textFont(font,12);
    pg.fill(TEXTCOLOR);
    pg.text("-",11,15);
    pg.text("o",25,15);

    //label
    pg.textFont(font,14);
    pg.textAlign(LEFT);
    pg.text(label,35,18);

    //clear button
    pg.fill(FILLCOLOR);
    if(mouseX - x > 4 && mouseX - x < 30 && mouseY - y > 59 && mouseY - y < 77){
      pg.fill(BUTTONOVER);
      if(mousePressed)pg.fill(BUTTONDOWN);
    }
    pg.rect(4,59,26,18);
    pg.textFont(font,12);    
    pg.textAlign(CENTER);    
    pg.fill(TEXTCOLOR);
    pg.text("CLR",17,75);

    //invert button
    pg.fill(FILLCOLOR);
    if(mouseX - x > 99 && mouseX - x < 116 && mouseY - y > 59 && mouseY - y < 77){
      pg.fill(BUTTONOVER);
      if(mousePressed)pg.fill(BUTTONDOWN);
    }    
    pg.rect(99,59,17,18);  
    pg.fill(TEXTCOLOR);
    pg.textFont(font,16);
    if(!invert) pg.text("+",108,75);    
    if(invert) pg.text("-",108,75);

    //input label
    pg.textAlign(LEFT);
    pg.textFont(font,14);
    if(input == -1) pg.text("Manual",34,76);
    if(input != -1) pg.text("Analog " + input,34,76);

    //slider
    pg.fill(BORDERCOLOR);
    pg.noStroke();
    pg.rect(4,38,112,4);
    pg.rect(4,34,2,12);
    pg.rect(114,34,2,12);
    pg.rect(56,34,2,12);

    if(dragActive) sliderX = constrain(mouseX-x,sliderMin,sliderMax);

    pg.fill(FILLCOLOR);
    pg.stroke(BORDERCOLOR);
    pg.rect(sliderX-sliderWidth/2,30,sliderWidth,20);


    value = (((sliderX-sliderMin) / (sliderMax-sliderMin)) - 0.5)/0.5;

    pg.popMatrix();
  }

  void setSlider(float v)
  {
    value = (v + 1.0)/2.0;
    if(invert) value = 1 - value;
    sliderX = value*(sliderMax-sliderMin) + sliderMin;
  }

  boolean mPress()
  {
    if(mouseX - x < 0 || mouseY - y < 0 || mouseX - x > 120 || mouseY - y > 80) return false;

    if(mouseX - x > 4 && mouseX - x < 16 && mouseY - y > 5 && mouseY - y < 17)
    {
      active = false;
      sliderX = sliderMin + (sliderMax-sliderMin)/2;
      input = -1;
      invert = false;
    }
    else if(mouseX - x > 18 && mouseX -x < 30 && mouseY -y > 5 && mouseY - y < 17){
      active = true;
    }
    else if(mouseX - x > 4 && mouseX - x < 30 && mouseY - y > 60 && mouseY - y < 78){
      input = -1;
    }
    else if(mouseX - x > sliderX-sliderWidth/2 && mouseX - x < sliderX + sliderWidth/2 && mouseY-y > 30 && mouseY-y < 50 && input == -1){
      dragActive = true;
      active = true;
    }
    else if(mouseX - x > 99 && mouseX - x < 116 && mouseY - y > 59 && mouseY - y < 77){
      invert = !invert;
    }else{
        dragX = mouseX;
        dragY = mouseY;
        isMoving = true;
    }     
    return true;
  }

  void mRelease(int whichController)
  {
    println(millis() + ": " + whichController);
    if(mouseX-x > 0 && mouseX-x < 120 &&  mouseY-y > 0 && mouseY-y < 80)
    {
      if(millis() - myLastClick < DOUBLECLICK)
      {
        println(millis() + " " + myLastClick + " " + DOUBLECLICK);
        layers.add(new PopText(ACTIVELAYER++,1,"New slider name:",0));
      }else{
        println("Single Click");
      }
      myLastClick = millis();
      if(whichController >= 0)
      {
        active = true;
        input = whichController;
      }
    }
    if(isMoving)
    {
      x += mouseX - dragX;
      y += mouseY - dragY;
      isMoving = false;
    }
    dragActive = false;    
  }

}
